import sys

from PyQt5.QtCore import Qt, QPoint, QRect
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QTextDocument, QTextCursor
from PyQt5.QtWidgets import QItemDelegate, QStyle, QApplication, QTableView


# 使用QItemDelegate进行单元格重绘：https://blog.csdn.net/Loc_Haoclass/article/details/106528047
class RichDelegate(QItemDelegate):
    __alignV = [Qt.AlignTop, Qt.AlignVCenter, Qt.AlignBottom]  # 【简化代码】
    __alignH = [Qt.AlignLeft, Qt.AlignHCenter, Qt.AlignRight]

    def __init__(self, parent=None):
        super().__init__(parent)

    def paint(self, ptr, opt, index):
        style = opt.widget.style() if opt.widget else QApplication.style()
        # 这条语句解决了行选中时背景色不变化的问题：https://blog.csdn.net/gongjianbo1992/article/details/108687172
        style.drawControl(QStyle.CE_ItemViewItem, opt, ptr, opt.widget)
        doc = QTextDocument()
        doc.setHtml(index.data())
        txDot = '…'  # 省略号
        rect = QRect(opt.rect)
        item = index.model().itemFromIndex(index)
        w = rect.width()
        s = set()
        # 使用hitTest判断字符能否完全显示：https://blog.csdn.net/eiilpux17/article/details/118461445
        for h in range(1, int(doc.size().height() / 10)):  # 逐行测试(该方法不严谨，小字体容易漏)
            h *= 10
            testPoint = QPoint(w, h)
            pos = doc.documentLayout().hitTest(testPoint, Qt.ExactHit)
            s.add(pos)
        s = sorted(s, reverse=True)

        if s[0] == -1:  # 只有一个结果，此时判断单元格是否过小
            if w < doc.size().width() - 10:  # 单元格大小不足以绘制
                return
        elif 0 in s:  # 空间不足，但又能够画个省略号
            doc.setHtml(txDot)
        else:
            for p in s:
                if p == -1:
                    continue
                cursor = QTextCursor(doc)
                cursor.setPosition(p - 1)
                cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor)
                cursor.insertText(txDot, cursor.block().charFormat())

        rect = self.__GetRect(rect, *self.__DepartAlign(item.textAlignment()), doc.size())
        ptr.save()
        ptr.translate(rect.topLeft())
        ptr.setClipRect(rect.translated(-rect.topLeft()))
        # doc.setDefaultTextOption(doc.defaultTextOption())
        doc.drawContents(ptr)
        ptr.restore()

    def __GetRect(self, rect, alignH, alignV, size):  # 根据align返回确切位置
        posV = [rect.top(), rect.bottom()]
        posH = [rect.left(), rect.right()]
        lenH, lenV = size.width(), size.height()
        for nape in [[alignV, self.__alignV, posV, lenV],
                     [alignH, self.__alignH, posH, lenH]]:
            align, alignLst, pos, width = nape
            index = alignLst.index(align)
            if index == 0:  # 靠左/靠上
                pos[1] = pos[0] + width
            elif index == 1:  # 居中
                pos[0] = pos[0] + int((pos[1] - pos[0] - width) / 2)
                pos[1] = pos[0] + width
            elif index == 2:  # 靠右/靠下
                pos[0] = pos[1] - width
        r = QRect(posH[0], posV[0], posH[1] - posH[0], posV[1] - posV[0])
        if r.width() > rect.width():
            r.setWidth(rect.width())
        return r

    def __DepartAlign(self, align):  # 将align拆解为水平和竖直并依次返回
        alignV = self.__alignV.copy()
        alignH = self.__alignH.copy()
        alignV = list(filter(lambda a: int(align) & int(a) != 0, alignV))
        alignH = list(filter(lambda a: int(align) & int(a) != 0, alignH))
        alignV.append(Qt.AlignVCenter)
        alignH.append(Qt.AlignLeft)
        return alignH[0], alignV[0]


if __name__ == '__main__':
    app = QApplication(sys.argv)

    tv = QTableView()
    model = QStandardItemModel()
    items = [
        QStandardItem('公司主要客户业务为<font color="#FFFF0000">水利工程</font><font color="#FF000000">。公司主营预应力钢筒混凝土</font><font '
                      'color="#FFFF0000">管道</font><font color="#FF000000">（</font><font '
                      'color="#FFFF0000">PCCP</font><font '
                      'color="#FF000000">）制造、运输，PCCP广泛应用于跨流域引水、输配水重点工程以及城市输水大中口径主干管网等。公司</font><font '
                      'color="#FFFF0000">风电塔筒</font><font color="#FF000000">业务已产生相关收益。</font>'),
        QStandardItem(
            '2222111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'
            '1111111111111111111111111111111111'
            '11111111111111111111'
            '1111111111111111111'
            '111111111111111111111'
            '11111111111111111111'),
        QStandardItem('<font color="#FFFF0000">水利工程</font><font color="#FF000000">'),
        QStandardItem('暂无')
    ]
    items1 = [
        QStandardItem('公司主要客户业务为<font color="#FFFF0000">水利工程</font><font color="#FF000000">。公司主营预应力钢筒混凝土</font><font '
                      'color="#FFFF0000">管道</font><font color="#FF000000">（</font><font '
                      'color="#FFFF0000">PCCP</font><font '
                      'color="#FF000000">）制造、运输，PCCP广泛应用于跨流域引水、输配水重点工程以及城市输水大中口径主干管网等。公司</font><font '
                      'color="#FFFF0000">风电塔筒</font><font color="#FF000000">业务已产生相关收益。</font>'),
        QStandardItem(
            '2222111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'
            '1111111111111111111111111111111111'
            '11111111111111111111'
            '1111111111111111111'
            '111111111111111111111'
            '11111111111111111111'),
        QStandardItem('<font color="#FFFF0000">水利工程</font><font color="#FF000000">'),
        QStandardItem('暂无')
    ]
    rich_2 = RichDelegate()
    tv.setItemDelegateForRow(1, rich_2)
    tv.setItemDelegateForRow(3, rich_2)
    model.appendRow(items)
    model.appendRow(items1)
    tv.setModel(model)
    tv.show()
    tv.resize(1200, 700)

    sys.exit(app.exec())
